package org.groupg.mockdatabyspring.morkdata.model;

import cn.hutool.core.text.UnicodeUtil;
import cn.hutool.json.JSONUtil;
import jakarta.validation.constraints.NotNull;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.StandardLineSeparator;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
import org.groupg.mockdatabyspring.morkdata.model.DBInfo.DBTableInfo;
import org.groupg.mockdatabyspring.morkdata.model.abstracts.CreateFileAbstract;
import org.groupg.mockdatabyspring.morkdata.model.enums.ColumnType;
import org.groupg.mockdatabyspring.morkdata.model.enums.DBDataFileStatus;

import java.io.File;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.LinkedList;
import java.util.List;

/**
 * 针对单表文件的数据创建格式
 */
@Setter
@Getter
@Slf4j
public class DBDataFile extends CreateFileAbstract {

    /**
     * 要创建的数据表模板是什么
     */
    private DBTableInfo dbTableInfo;

    /**
     * 要创建的文件全路径是什么
     */
    private String absoluteFilePath;
    /**
     * 要最大处理多少行数据？
     */
    private Integer maxRowNum;
    /**
     * 创建时是否添加标题，标题来源于每个字段的名字
     * {@link org.groupg.mockdatabyspring.morkdata.model.DBInfo.DBTableInfo#getTitleString(java.lang.String)}
     */
    private boolean isHaveTitle;


    /**
     * 默认的行分隔符
     */
    static byte[] defaultLineSplit = {0x01};
    /**
     * 默认的字段分隔符
     */
    static byte[] defaultFieldSplit = {0x02};

    /**
     * 行分隔符
     */
    private String lineSplit = new String(defaultLineSplit)+"\n";
    /**
     * 字段分隔符
     */
    private String fieldSplit = new String(defaultFieldSplit);;
    /**
     * 创建文件的字符集
     */
    private String charsetName = Charset.defaultCharset().name();

    /**
     * 运行状态，{@link  DBDataFileStatus}
     */
    private String status = DBDataFileStatus.READY;

    /**
     * 创建基础对象
     *
     * @param dbTableInfo      {@link DBTableInfo} 对象，内容包括表名等基础信息
     * @param absoluteFilePath 文件绝对路径
     * @param maxRowNum        生成行数
     * @param isHaveTitle      是否包含标题
     * @param fieldSplit       字段分隔符
     * @param lineSplit        行分隔符
     * @param charsetName      字符集
     */
    public DBDataFile(@NotNull(message = "请设置数据表的相关内容！") DBTableInfo dbTableInfo, @NotNull(message = "请设置文件全路径！") String absoluteFilePath, @NotNull(message = "请设置文件除标题行外的行数") Integer maxRowNum, @NotNull(message = "请设置是否包含标题") boolean isHaveTitle, @NotNull(message = "请设置字段分隔符！") String fieldSplit, String lineSplit, @NotNull(message = "请设置字符集！") String charsetName) {
        this.dbTableInfo = dbTableInfo;
        this.absoluteFilePath = absoluteFilePath;
        this.maxRowNum = maxRowNum;
        this.isHaveTitle = isHaveTitle;
        this.fieldSplit = UnicodeUtil.toString(fieldSplit);
        this.lineSplit = UnicodeUtil.toString(lineSplit);
        this.charsetName = charsetName;
    }

    /**
     * 默认构造器
     */
    public DBDataFile() {

    }

    /**
     * 根据默认构造器完成基础内容的检查与构造
     *
     * @param dbTableInfo      表基础信息
     * @param absoluteFilePath 文件的绝对路径路径
     * @param maxRowNum        最大行数
     * @param isHaveTitle      是否需要包含标题
     * @param fieldSplit       字段与字段之间的分隔符
     * @param lineSplit        行分隔符
     * @param charsetName      生成文件的字符集
     * @return 本实例对象
     */
    public static DBDataFile createModel(DBTableInfo dbTableInfo, String absoluteFilePath, Integer maxRowNum, boolean isHaveTitle, String fieldSplit, String lineSplit, String charsetName) {
        if (StringUtils.isEmpty(lineSplit)) {
            lineSplit = System.lineSeparator();
        }
        if (StringUtils.isEmpty(charsetName)) {
            charsetName = "UTF-8";
        }
        return new DBDataFile(dbTableInfo, absoluteFilePath, maxRowNum, isHaveTitle, fieldSplit, lineSplit, charsetName);
    }

    /**
     * 创建一个默认 CSV 格式的文件
     *
     * @param dbTableInfo      表基础信息
     * @param absoluteFilePath 文件的绝对路径
     * @param maxRowNum        文件最大行数
     * @param isHaveTitle      是否包含标题
     * @param charsetName      文件字符集
     * @return 本实例对象
     */
    public static DBDataFile createCSVModel(DBTableInfo dbTableInfo, String absoluteFilePath, Integer maxRowNum, boolean isHaveTitle, String charsetName) {
        return createModel(dbTableInfo, absoluteFilePath, maxRowNum, isHaveTitle, ",", System.lineSeparator(), charsetName);
    }

    /**
     * 创建一个默认 TSV 格式的文件
     *
     * @param dbTableInfo      表基础信息
     * @param absoluteFilePath 文件的绝对路径
     * @param maxRowNum        文件最大行数
     * @param isHaveTitle      是否包含标题
     * @param charsetName      文件字符集
     * @return 本实例对象
     */
    public static DBDataFile createTSVModel(DBTableInfo dbTableInfo, String absoluteFilePath, Integer maxRowNum, boolean isHaveTitle, String charsetName) {
        return createModel(dbTableInfo, absoluteFilePath, maxRowNum, isHaveTitle, "\t", System.lineSeparator(), charsetName);
    }

    /**
     * 创建一个默认 CSV 格式 UTF-8 字符集的文件
     *
     * @param dbTableInfo      表基础信息
     * @param absoluteFilePath 文件的绝对路径
     * @param maxRowNum        文件最大行数
     * @param isHaveTitle      是否包含标题
     * @return 本实例对象
     */
    public static DBDataFile createUTF8CSVModel(DBTableInfo dbTableInfo, String absoluteFilePath, Integer maxRowNum, boolean isHaveTitle) {
        return createCSVModel(dbTableInfo, absoluteFilePath, maxRowNum, isHaveTitle, StandardCharsets.UTF_8.name());
    }

    /**
     * 创建一个默认 TSV 格式 UTF-8 字符集的文件
     *
     * @param dbTableInfo      表基础信息
     * @param absoluteFilePath 文件的绝对路径
     * @param maxRowNum        文件最大行数
     * @param isHaveTitle      是否包含标题
     * @return 本实例对象
     */
    public static DBDataFile createUTF8TSVModel(DBTableInfo dbTableInfo, String absoluteFilePath, Integer maxRowNum, boolean isHaveTitle) {
        return createTSVModel(dbTableInfo, absoluteFilePath, maxRowNum, isHaveTitle, StandardCharsets.UTF_8.name());
    }

    /**
     * 创建一个 UTF-8 字符集的文件
     *
     * @param dbTableInfo      表基础信息
     * @param absoluteFilePath 文件的绝对路径
     * @param maxRowNum        文件最大行数
     * @param isHaveTitle      是否包含标题
     * @param fieldSplit       字段分隔符
     * @param lineSplit        行分隔符
     * @return 本实例对象
     */
    public static DBDataFile createUTF8Model(DBTableInfo dbTableInfo, String absoluteFilePath, Integer maxRowNum, boolean isHaveTitle, String fieldSplit, String lineSplit) {
        return createModel(dbTableInfo, absoluteFilePath, maxRowNum, isHaveTitle, fieldSplit, lineSplit, StandardCharsets.UTF_8.name());
    }

    /**
     * main 方法
     *
     * @param args 方法参数
     */
    public static void main(String[] args) {

        DBTableInfo dbTableInfo1 = new DBTableInfo("SYS_USER", "APP", "APP", "用户表");
        dbTableInfo1.addColumnInfo("ACCOUNT", ColumnType.STRING, Boolean.TRUE, Boolean.FALSE, 60, 0, "[0-9]{6}", "用户主键数据")
                .addColumnInfo("CNNAME", ColumnType.STRING, Boolean.FALSE, Boolean.TRUE, 60, 0, "[赵|钱|孙|李|周|吴|郑|王]{1}x{1,2}", "用户名")
                .addColumnInfo("EMAIL", ColumnType.STRING, Boolean.FALSE, Boolean.TRUE, 60, 0, "([a-z]|[A-Z]){6,8}\\@[0-9]{3}\\.com", "邮箱地址")
                .addColumnInfo("Mobile", ColumnType.STRING, Boolean.FALSE, Boolean.TRUE, 60, 0, "1[3-9]\\d{9}", "电话号码")
                .addColumnInfo("USERSTATE", ColumnType.STRING, Boolean.FALSE, Boolean.TRUE, 60, 0, "(ONJOB|RESIGN)", "用户状态")
                .addColumnInfo("ORGCODE", ColumnType.STRING, Boolean.FALSE, Boolean.TRUE, 60, 0, "[0-9]{5}0?", "所属机构")
                .addColumnInfo("ORGCODE", ColumnType.STRING, Boolean.FALSE, Boolean.TRUE, 60, 0, "[0-9]{5}0?", "所属机构")
                .addColumnInfo("CREATETIME", ColumnType.STRING, Boolean.FALSE, Boolean.TRUE, 60, 0, "20[0-9]{2}-[01]{1}[0-9]{1}-[012]{1}[0-9]{1}", "创建日期")
                .addColumnInfo("CERTTYPE", ColumnType.STRING, Boolean.FALSE, Boolean.TRUE, 60, 0, "10|20|30", "证件类型")
                .addColumnInfo("CERTNO", ColumnType.STRING, Boolean.FALSE, Boolean.TRUE, 60, 0, "\\d{17}[0-9Xx]|\\d{15}", "证件编号");
        DBDataFile dBDataFile = DBDataFile.createUTF8Model(dbTableInfo1, "F:\\DATA\\demo\\Demo.csv", 500000, Boolean.TRUE, ",", StandardLineSeparator.LF.getString());
//        String jsonStr = JSONUtil.toJsonStr(DBDataFile);
        System.out.println(JSONUtil.toJsonPrettyStr(dBDataFile));
    }

    /**
     * 获取当前对象的是否包含标题的属性
     *
     * @return 是否包含标题
     */
    public boolean getIsHaveTitle() {
        return this.isHaveTitle;
    }

    @Override
    public boolean check() {
        boolean b = true;
        log.debug("对当前对象实例进行检查");
        return b;
    }

    @Override
    public void createFile() throws IOException {

        this.setStatus(DBDataFileStatus.RUNNING);

        log.debug("启动创建文件");
        File file = FileUtils.getFile(this.absoluteFilePath);

        if (!file.getParentFile().exists()) {
            // 如果父文件夹存在并且为文件夹则删除重建
//            FileUtils.deleteDirectory(file.getParentFile());
            file.getParentFile().mkdirs();
        }
        if (file.isFile()) {
            // 如果文件存在则删除文件并重新创建
            file.delete();
            file.createNewFile();
        }
        if (this.getIsHaveTitle()) {
            FileUtils.write(file, this.getDbTableInfo().getTitleString(this.getFieldSplit()) + this.getLineSplit(), Charset.forName(this.getCharsetName()));
        }
        int maxRam = 10000; // 字段数与行数的积 一次写文件内存中保留的内容
        int m = maxRam/dbTableInfo.getColumnInfos().size(); // 一次要处理多少行

        int x = this.getMaxRowNum() / m;
        if (x > 1) {
            log.debug("文件内容较多，需要按每次{}分解。",(x+1));

            for (int i = 0; i < x + 1; i++) {

                if (i == x)
                    m = getMaxRowNum() % m;
                appendLines(file,m);
                log.debug("分解第{}/{}次", i + 1,(x+1));
            }
        } else {
            log.debug("文件内容较少，一次性写入。");
            appendLines(file);
        }
        log.debug("完成文件创建，文件路径为：{}", this.absoluteFilePath);
        this.setStatus(DBDataFileStatus.OVER);
    }

    /**
     * 针对当前行完成数据录入到文件的过程
     *
     * @param file 文件对象
     * @param x 要处理的行数
     * @throws IOException IO 异常
     */
    public void appendLines(File file, int x) throws IOException {
        List<String> lines = new LinkedList<>();
        for (int j = 0; j < x; j++) {
            lines.add(this.dbTableInfo.getLineString(this.getFieldSplit()));
        }
        FileUtils.writeLines(file, this.getCharsetName(), lines, this.getLineSplit(), true);
    }

    /**
     * 拼接文件内容的方式方法
     * @param file 原始文件
     * @throws IOException 文件IO流的异常
     */
    public void appendLines(File file) throws IOException {
        appendLines(file,getMaxRowNum());
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;

        if (o == null || getClass() != o.getClass()) return false;

        DBDataFile that = (DBDataFile) o;

        return new EqualsBuilder().append(isHaveTitle, that.isHaveTitle).append(getDbTableInfo(), that.getDbTableInfo()).append(getAbsoluteFilePath(), that.getAbsoluteFilePath()).append(getMaxRowNum(), that.getMaxRowNum()).append(getLineSplit(), that.getLineSplit()).append(getFieldSplit(), that.getFieldSplit()).append(getCharsetName(), that.getCharsetName()).isEquals();
    }

    @Override
    public int hashCode() {
        return new HashCodeBuilder(17, 37).append(getDbTableInfo()).append(getAbsoluteFilePath()).append(getMaxRowNum()).append(isHaveTitle).append(getLineSplit()).append(getFieldSplit()).append(getCharsetName()).toHashCode();
    }
}
